﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Merging;
using System.Diagnostics;
using Engine.Sgml.Validation;
using Engine.Sgml.Validation.Rules;
using Engine.Sgml.Matching;

namespace Engine.Sgml.Allocation {
    internal sealed class InconsistentBlockAllocator {
        private ModelMatcher modelMatcher;
        private MatchMerger matchMerger;

        public InconsistentBlockAllocator() {
            this.modelMatcher = new ModelMatcher();
            this.matchMerger = new MatchMerger();
            }

        public IList<InconsistentBlock> Allocate(InconsistentBlock block, SElement ancestor, AllocationContext context) {
            Guard.ArgumentNotNull(block, "block");
            Guard.ArgumentNotNull(ancestor, "ancestor");
            Guard.ArgumentNotNull(context, "context");

            // Remove all inconsistent blocks from ancestor
            // where the specified block will be allocated
            IList<InconsistentBlock> blocks;
            if (context.Blocks.TryGetBlocks(ancestor, out blocks))
                for (int i = 0; i < blocks.Count; i++) {
                    blocks[i].RemoveFromAncestor();
                    }

            IList<Capture> captures = modelMatcher.Match(ancestor);
            IList<InconsistentBlock> inconsistentBlocks = matchMerger.Merge(captures);

            if (inconsistentBlocks.Count > 0) {
                bool[] items = MarkUnallocatedElements(block, inconsistentBlocks);
                for (int i = items.Length - 1; i >= 0; i--)
                    if (!items[i])
                        block.RemoveAt(i);
                RemoveAllocatedElements(inconsistentBlocks, block);
                }
            else
                block.Clear();

            // Restore all inconsistent blocks in the ancestor
            if (blocks != null) {
                for (int i = 0; i < blocks.Count; i++) {
                    blocks[i].Restore();
                    }
                for (int i = 0; i < inconsistentBlocks.Count; i++) {
                    inconsistentBlocks[i].RecalculateStartIndex();
                    }
                }
            return inconsistentBlocks;
            }

        private static bool[] MarkUnallocatedElements(InconsistentBlock block, IList<InconsistentBlock> blocks) {
            bool[] items = new bool[block.Count];
            for (int i = 0; i < blocks.Count; i++) {
                InconsistentBlock b = blocks[i];
                for (int j = 0; j < b.Count; j++) {
                    int index;
                    SElement element = b[j];
                    if (FindByReference(element, block, out index) || FindByName(element, block, out index)) {
                        // Unallocated element
                        items[index] = true;
                        }
                    }
                }
            return items;
            }

        private static void RemoveAllocatedElements(IList<InconsistentBlock> blocks, InconsistentBlock block) {
            if (block.Count == 0) {
                for (int i = 0; i < blocks.Count; i++) {
                    blocks[i].Clear();
                    }
                }
            else
                for (int i = 0; i < blocks.Count; i++) {
                    InconsistentBlock b = blocks[i];
                    for (int j = 0; j < b.Count; j++) {
                        int index;
                        if (!FindByReference(b[j], block, out index))
                            b.RemoveAt(j--);
                        }
                    if (b.Count == 0)
                        blocks.RemoveAt(i--);
                    }
            }

        private static bool FindByReference(SElement element, InconsistentBlock block, out int index) {
            for (int i = 0; i < block.Count; i++)
                if (object.ReferenceEquals(block[i], element)) {
                    index = i;
                    return true;
                    }
            index = -1;
            return false;
            }

        private static bool FindByName(SElement element, InconsistentBlock block, out int index) {
            for (int i = 0; i < block.Count; i++)
                if (ElementMatcher.Match(block[i], element)) {
                    index = i;
                    return true;
                    }
            index = -1;
            return false;
            }
        }
    }
